use std::{time::Duration, usize};

pub type LogStr = arraystring::SmallString;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum NumLog {
    None,
    S(&'static str),
    I(i64),
    F(f64),
    Str(LogStr),
    Dur(Duration),
    Time(chrono::NaiveTime),
}

impl NumLog {
    pub fn is_numeric(&self) -> bool {
        match self {
            NumLog::None => false,
            NumLog::S(_) => false,
            NumLog::I(_) => true,
            NumLog::F(_) => true,
            NumLog::Str(_) => false,
            NumLog::Dur(_) => true,
            NumLog::Time(_) => true,
        }
    }
}

impl Default for NumLog {
    fn default() -> Self {
        NumLog::None
    }
}

impl std::fmt::Display for NumLog {
    fn fmt(&self, format: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            NumLog::None => write!(format, ""),
            NumLog::S(s) => write!(format, "{s}"),
            NumLog::I(i) => write!(format, "{i}"),
            NumLog::F(f) => write!(format, "{f:.2}"),
            NumLog::Str(s) => write!(format, "{s}"),
            NumLog::Dur(s) => write!(format, "{s:?}"),
            NumLog::Time(b) => write!(format, "{}", b.format("%H:%M:%S%.6f")),
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct NumLogs<const L: usize> {
    title: &'static str,
    num_log: [NumLog; L],
    index: usize,
}

impl<const L: usize> NumLogs<L> {
    pub fn new(title: &'static str) -> Self {
        let r = [Default::default(); L];
        return Self {
            title,
            num_log: r,
            index: 0,
        };
    }

    pub fn get_title(&self) -> &'static str {
        self.title
    }

    pub fn is_empty(&self) -> bool {
        self.index == 0
    }

    pub fn len(&self) -> usize {
        self.index
    }

    pub fn clear(&mut self) {
        self.index = 0;
    }

    #[inline(always)]
    pub fn add(&mut self, n: impl IntoNumLog) -> &mut Self {
        let n = n.into_num_log();
        if self.index >= L {
            #[cfg(debug_assertions)]
            eprintln!("NumLogs is full({} >= {}): {n}", self.index, L);

            self.index += 1;
            return self;
        }
        self.num_log[self.index] = n;
        self.index += 1;
        self
    }

    #[inline(always)]
    pub fn push(&mut self, s: (&'static str, String)) -> &mut Self {
        self.add(s.0).add(s.1)
    }

    #[inline(always)]
    pub fn push_arr(&mut self, arr: Vec<(&'static str, String)>) -> &mut Self {
        for ele in arr {
            self.push(ele);
        }
        self
    }

    #[inline(always)]
    pub fn add2(&mut self, n: impl IntoNumLog, n2: impl IntoNumLog) -> &mut Self {
        self.add(n).add(n2)
    }

    #[inline(always)]
    pub fn add_whitespace(&mut self) -> &mut Self {
        self.add(", ")
    }

    #[inline(always)]
    pub fn add_k(&mut self) -> &mut Self {
        self.add(": ")
    }

    #[inline(always)]
    pub fn add_arr<const ARR_LEN: usize, T: IntoNumLog>(
        &mut self,
        s_arr: [T; ARR_LEN],
    ) -> &mut Self {
        for ele in s_arr {
            let ele = ele.into_num_log();
            self.add(ele);
        }
        self
    }

    #[inline(always)]
    pub fn add_vec<T: IntoNumLog>(&mut self, s_arr: Vec<T>) -> &mut Self {
        for ele in s_arr {
            let ele = ele.into_num_log();
            self.add(ele);
        }
        self
    }

    #[inline(always)]
    pub fn append<const LEN: usize>(&mut self, vals: NumLogs<LEN>) -> &mut Self {
        let mut i = vals.index;
        for ele in vals.num_log {
            self.add(ele);
            i -= 1;
            if i == 0 {
                break;
            }
        }
        self
    }
}

pub trait IntoNumLog {
    fn into_num_log(self) -> NumLog;
}

impl IntoNumLog for NumLog {
    fn into_num_log(self) -> NumLog {
        self
    }
}

impl IntoNumLog for &'static str {
    fn into_num_log(self) -> NumLog {
        NumLog::S(self)
    }
}

impl IntoNumLog for String {
    fn into_num_log(self) -> NumLog {
        NumLog::Str(self.as_str().into())
    }
}

impl IntoNumLog for i32 {
    fn into_num_log(self) -> NumLog {
        NumLog::I(self as i64)
    }
}

impl IntoNumLog for i64 {
    fn into_num_log(self) -> NumLog {
        NumLog::I(self)
    }
}

impl IntoNumLog for usize {
    fn into_num_log(self) -> NumLog {
        NumLog::I(self as i64)
    }
}

impl IntoNumLog for u32 {
    fn into_num_log(self) -> NumLog {
        NumLog::I(self as i64)
    }
}

impl IntoNumLog for u64 {
    fn into_num_log(self) -> NumLog {
        NumLog::I(self as i64)
    }
}

impl IntoNumLog for f32 {
    fn into_num_log(self) -> NumLog {
        NumLog::F(self as f64)
    }
}

impl IntoNumLog for f64 {
    fn into_num_log(self) -> NumLog {
        NumLog::F(self)
    }
}

impl IntoNumLog for Duration {
    fn into_num_log(self) -> NumLog {
        NumLog::Dur(self)
    }
}

impl IntoNumLog for chrono::Duration {
    fn into_num_log(self) -> NumLog {
        NumLog::Dur(self.to_std().unwrap_or_else(|_| Duration::ZERO))
    }
}

impl IntoNumLog for chrono::NaiveDateTime {
    fn into_num_log(self) -> NumLog {
        NumLog::Time(self.time())
    }
}

impl IntoNumLog for chrono::NaiveDate {
    fn into_num_log(self) -> NumLog {
        self.format("%Y-%m-%d").to_string().into_num_log()
    }
}

impl IntoNumLog for chrono::NaiveTime {
    fn into_num_log(self) -> NumLog {
        NumLog::Time(self)
    }
}

impl<const L: usize> std::fmt::Display for NumLogs<L> {
    fn fmt(&self, format: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut s = String::new();
        for i in 0..self.index {
            if i == L {
                break;
            }
            let v = &self.num_log[i];
            s.push_str(&v.to_string());
            if v.is_numeric() {
                s.push_str(", ");
            }
        }
        write!(format, "{}", s.trim_end_matches(", "))?;
        Ok(())
    }
}

pub trait NumTrait {
    fn num(self) -> i64;
}

impl NumTrait for i64 {
    fn num(self) -> i64 {
        self
    }
}

impl NumTrait for i128 {
    fn num(self) -> i64 {
        self as i64
    }
}

impl NumTrait for i32 {
    fn num(self) -> i64 {
        self as i64
    }
}

impl NumTrait for usize {
    fn num(self) -> i64 {
        self as i64
    }
}

#[test]
fn test() {
    let s = LogStr::from("123456789");
    println!(
        "num_logs 123456789 size_of_val: {}",
        std::mem::size_of_val(&s)
    );
    println!("LogStr: {s}");

    let s = LogStr::from("一二三四五六七八九");
    println!(
        "num_logs 一二三四五六七八九 size_of_val: {}",
        std::mem::size_of_val(&s)
    );
    println!("LogStr: {s}");

    let time = std::time::Instant::now();
    let mut num_logs = NumLogs::<32>::new("test log");
    println!("num_logs::new() elapsed: {:?}", time.elapsed());

    println!("num_logs size_of_val: {}", std::mem::size_of_val(&num_logs));
    num_logs
        .add("is a ")
        .add("string; ")
        .add(1)
        .add(2)
        .add(3)
        .add("floatVal: ")
        .add(123.45678)
        .add(2222.0)
        .add("int64Val: ")
        .add(3333_i64)
        .add(4444_i32)
        .add("字符串数组: ".to_string())
        .add_arr([5555, 66666, 7777])
        .add2("时间: ", chrono::Local::now().naive_local())
        .add2("时间-date: ", chrono::Local::now().naive_local().date())
        .add_whitespace()
        .add2("时间-time: ", chrono::Local::now().naive_local().time())
        .add_arr([5555, 66666, 7777])
        .add2("耗时: ", time.elapsed())
        .add(8888);
    println!("num_logs: {}", num_logs);

    let mut num_logs = NumLogs::<10>::new("test log: ");
    for i in 1..=9 {
        num_logs.add(i);
    }
    println!("num_logs<10>: {}", num_logs);

    let mut num_logs = NumLogs::<10>::new("test log: ");
    num_logs.add2("num1:", 1);
    num_logs.add2("num2:", 2);
    num_logs.add("num3");
    println!("num_logs<5>: {}", num_logs);

    let mut num_logs2 = NumLogs::<20>::new("test log: ");
    for i in 1..=10 {
        num_logs2.add(i);
    }
    num_logs2.append(num_logs);
    num_logs2.add("; end");
    println!("num_logs2: {}", num_logs2);
}
